﻿
Imports std
Imports runrroom

Public Class jobmgr_t
    Private Class job
        Public Const processOutputSuffix As String = ".output"
        Private _jobid As String = Nothing
        Private WithEvents _proc As Process = Nothing
        Private _percent As percentage_t = Nothing
        Private _startticks As Int64 = 0
        Private _endticks As Int64 = 0
        Private _extcode As Int64 = 0
        Private _exited As Boolean = False

        Private Function processOutput() As String
            Static _processOutput As String = Nothing
            If _processOutput Is Nothing Then
                createDirectory(runrroom.defaultValue.localrunProcessOutputDirectory)
                _processOutput = combinepath(runrroom.defaultValue.localrunProcessOutputDirectory, _
                                             _jobid + processOutputSuffix)
            End If
            Return _processOutput
        End Function

        Private Sub receive(ByVal e As Diagnostics.DataReceivedEventArgs)
            Try
                SyncLock _proc
                    appendfile(processOutput(), e.Data() + newline.incode())
                End SyncLock
            Catch ex As Exception
                raiseError(errorHandle.errorType.warning, _
                           "cannot write process ", processFilename(_proc), _
                           " output to file ", processOutput(), _
                           ", jobid ", _jobid, ", ex ", ex.Message)
            End Try
        End Sub

        Private Sub _proc_ErrorDataReceived(ByVal sender As Object, _
                                            ByVal e As Diagnostics.DataReceivedEventArgs) _
                                           Handles _proc.ErrorDataReceived
            receive(e)
        End Sub

        Private Sub _proc_OutputDataReceived(ByVal sender As Object, _
                                             ByVal e As Diagnostics.DataReceivedEventArgs) _
                                            Handles _proc.OutputDataReceived
            receive(e)
        End Sub

        Public Sub updateProcessInfo()
            If Not _exited Then
                If _proc Is Nothing Then
                    _endticks = Now.Ticks()
                    _extcode = npos
                    _exited = True
                Else
                    If processExited(_proc) Then
                        If Not processExitTime(_proc, _endticks) Then
                            _endticks = Now.Ticks()
                        End If
                        If Not processExitCode(_proc, _extcode) Then
                            _extcode = npos
                        End If
                        _percent = percentage_t.FINISHED
                        _exited = True
                    End If
                End If
            End If
        End Sub

        Public Function startticks() As Int64
            Return _startticks
        End Function

        Public Function endticks() As Int64
            Return _endticks
        End Function

        Public Function extcode() As Int64
            Return _extcode
        End Function

        Public Function exited() As Boolean
            Return _exited
        End Function

        Public Function jobid() As String
            Return _jobid
        End Function

        Public Function proc() As Process
            Return _proc
        End Function

        Public Sub New(ByVal jobid As String, ByVal app As String, ByVal arg As String)
            assert(Not isemptystring(jobid))
            Dim beforestart As _do(Of Process, Boolean) =
                Function(ByRef p As Process) As Boolean
                    p.StartInfo.RedirectStandardOutput = True
                    p.StartInfo.RedirectStandardError = True
                    Return True
                End Function
            copy(_jobid, jobid)
            startprocess(_proc, combinepath(fullpath(delegate_path), app), arg, beforestart, _
                         environment_variables.localrunJobId, jobid)
            If Not _proc Is Nothing Then
                _percent = New percentage_t(0)
                _startticks = Now.Ticks()
                _endticks = npos
                _extcode = 0
                _proc.BeginOutputReadLine()
                _proc.BeginErrorReadLine()
            Else
                _percent = percentage_t.FAILED
                _startticks = npos
                _endticks = npos
                _extcode = npos
                _exited = True
            End If
        End Sub

        Public Property percent() As percentage_t
            Get
                Return copy(_percent)
            End Get
            Set(ByVal value As percentage_t)
                copy(_percent, value)
            End Set
        End Property

        Protected Overrides Sub Finalize()
            If Not _proc Is Nothing Then
                _proc.Dispose()
                _proc = Nothing
            End If
            MyBase.Finalize()
        End Sub
    End Class

    Private jobs As map(Of String, job) = Nothing
    Private locker As sequentialLock = Nothing
    Private timer3Index As Guid = Guid.Empty

    Public Sub New()
        jobs = New map(Of String, job)()
        locker = New sequentialLock()
        timer3Index = timer3.addcall(AddressOf updateJobInfo, secondsToMilliseconds(5))
    End Sub

    Private Sub updateJobInfo()
        lock()
        Dim removelist As vector(Of String) = Nothing
        removelist = New vector(Of String)()
        Dim it As map(Of String, job).iterator = Nothing
        it = jobs.begin()
        While it <> jobs.end()
            With +it
                .second.updateProcessInfo()
                If .second.exited() AndAlso _
                   ticksToSeconds(Now.Ticks() - .second.endticks()) >= config.reserveJobInfoSeconds Then
                    removelist.push_back(.first())
                End If
            End With
            it += 1
        End While

        For i As Int64 = 0 To removelist.size() - 1
            jobs.erase(removelist(i))
        Next
        unlock()
    End Sub

    Private Sub lock()
        locker.wait()
    End Sub

    Private Sub unlock()
        locker.release()
    End Sub

    Private Function at(ByVal jobid As String, ByRef job As job) As Boolean
        lock()
        Dim rtn As Boolean = False
        Dim it As map(Of String, job).iterator = Nothing
        it = jobs.find(jobid)
        If it = jobs.end() Then
            rtn = False
        Else
            job = (+it).second
            rtn = True
        End If
        unlock()
        Return rtn
    End Function

    Public Function hasjob(ByVal jobid As String) As Boolean
        Return at(jobid, Nothing)
    End Function

    Public Function percent(ByVal jobid As String) As percentage_t
        Dim p As percentage_t = Nothing
        If percent(jobid, p) Then
            Return p
        Else
            Return percentage_t.FAILED
        End If
    End Function

    Public Function percent(ByVal jobid As String, ByRef p As percentage_t) As Boolean
        Dim job As job = Nothing
        If at(jobid, job) Then
            p = job.percent()
            Return True
        Else
            Return False
        End If
    End Function

    Public Function updatePercent(ByVal jobid As String, ByVal p As percentage_t) As Boolean
        Dim job As job = Nothing
        If at(jobid, job) Then
            job.percent() = p
            Return True
        Else
            Return False
        End If
    End Function

    Public Function addjob(ByVal jobid As String, ByVal cmd As String) As Boolean
        Dim app As String = Nothing
        Dim arg As String = Nothing
        If splitCommand(cmd, app, arg) Then
            lock()
            jobs(jobid) = New job(jobid, app, arg)
            unlock()
            Return True
        Else
            raiseError("cannot split command " + cmd, errorHandle.errorType.warning)
            Return False
        End If
    End Function

    Protected Overrides Sub Finalize()
        If timer3Index <> Guid.Empty Then
            assert(timer3.removecall(timer3Index))
            timer3Index = Guid.Empty
        End If
        MyBase.Finalize()
    End Sub
End Class
